library(ggplot2)
library(dplyr)
library(tidyr)
library("ggpubr")
library(LDATS)
library(ggVennDiagram)
library(stringr)
library(abind)
source("utils.R")
Visualization on CIFAR100. We are using data of three neural networks trained on reduced CIFAR100 training set. Half of the CIFAR100 training set was extracted as a validation set. We then divided both the reduced training set and validation set into 5 disjoint subsets and trained an ensemble on each of them. This was done in 10 replications, each time with random split of the training set into validation and new training set. In this visualization, we are trying to inspect the outputs deeper, mainly to make sense of strange behavior of nll metric for ensemble outputs.
base_dir <- "../data/data_train_val_half_c100"
repls <- 0:9
folds <- 0:4
classes <- 100
nets_outputs <- load_network_outputs(base_dir, repls)
ens_outputs <- load_ensemble_outputs(base_dir, repls, folds, gather=(1 + nets_outputs$test_labels[1, ]))
sort_ind <- function(lst)
{
return(sort(lst, index.return=TRUE, decreasing=TRUE)$ix)
}
nets_test_top_indices <- apply(X=nets_outputs$test_outputs, MARGIN=c(1, 2, 3), FUN=sort_ind)[1, , , ]
r_n <- length(repls)
samples_n <- dim(nets_outputs$test_labels)[2]
nets_n <- length(nets_outputs$networks)
test_labs <- nets_outputs$test_labels + 1
dim(test_labs) <- c(r_n, 1, samples_n)
test_labs <- aperm(abind(array(rep(aperm(test_labs, perm=c(2, 1, 3)), nets_n), c(r_n, samples_n, nets_n)), along=3), perm=c(1, 3, 2))
nets_test_cor_preds <- test_labs == nets_test_top_indices
for (ri in 1:r_n)
{
nets_cor_list <- list()
incor <- 1:samples_n
for (ni in 1:nets_n)
{
cor_list <- which(nets_test_cor_preds[ri, ni, ])
nets_cor_list[[nets_outputs$networks[ni]]] = cor_list
incor <- setdiff(incor, cor_list)
}
incor_n <- length(incor)
venn_diag <- ggVennDiagram(nets_cor_list) + scale_fill_gradient(trans="log10", name="count", limits=c(1, 10000)) +
annotate(geom="text", x=-4, y=5, label=paste("Incorrect ", incor_n, "\n", round(incor_n / samples_n * 100), "%")) +
ggtitle(paste("Correct predictions by network - replication ", ri)) +
scale_x_continuous(limits=c(-8, 10))
print(venn_diag)
}










Compared to CIFAR10 examples, here are fewer samples correctly classified by all the networks. For one network exclusive correct classifications densenet and xception perform better than resnet. Pairs of network all have similar values with xception, resnet lagging a bit.
preds <- nets_outputs$test_outputs
for (ri in repls + 1)
{
for (net_i in seq_along(nets_outputs[["networks"]]))
{
preds[ri, net_i, ,] <- softmax(preds[ri, net_i, , ])
}
}
nets_test_cor_probs <- gather(preds, 1 + nets_outputs$test_labels[1, ], 3, 4)
nets_test_cor_probs <- melt(nets_test_cor_probs)
nets_test_cor_probs <- nets_test_cor_probs[, c(-3, -4)]
names(nets_test_cor_probs) <- c("replication", "network", "prediction")
nets_test_cor_probs$network <- as.factor(nets_test_cor_probs$network)
levels(nets_test_cor_probs$network) <- nets_outputs$networks
nets_cor_preds_histo <- ggplot(data=nets_test_cor_probs) + geom_histogram(mapping=aes(x=prediction), binwidth=0.01) +
ggtitle("Histograms of predicted probability for the correct class") + facet_wrap(~network) + scale_y_log10()
nets_cor_preds_histo

levels(val_ens_cor_probs$combining_method) <- ens_outputs$combining_methods
Error: object 'ens_outputs' not found
val_ens_cor_preds_histo <- ggplot(data=val_ens_cor_probs) + geom_histogram(mapping=aes(x=prediction), binwidth=0.01) + facet_grid(rows=vars(combining_method), cols=vars(coupling_method)) + scale_y_log10() + ggtitle("Probabilities predicted for the correct class - ens trained on val")
val_ens_cor_preds_histo

sum(val_ens_cor_probs$prediction <= 0)
[1] 0
No zero probabilities produced
train_ens_cor_preds_histo <- ggplot(data=train_ens_cor_probs) + geom_histogram(mapping=aes(x=prediction), binwidth=0.01) + facet_grid(rows=vars(combining_method), cols=vars(coupling_method)) + scale_y_log10() + ggtitle("Probabilities predicted for the correct class - ens trained on train")
train_ens_cor_preds_histo
Warning: Transformation introduced infinite values in continuous y-axis
Warning: Removed 63 rows containing missing values (geom_bar).

Strange behavior observed for bc coupling method in case of combining method lda is not present for combining methods logreg.
train_ens_zero_counts <- ggplot(data=train_ens_cor_probs[train_ens_cor_probs$prediction <= 0, ]) + geom_histogram(mapping=aes(x=coupling_method), stat="count") + facet_wrap(~combining_method) + ggtitle("Counts of zero or lower probabilities predicted for the correct class by coup m\nTrain training")
Warning: Ignoring unknown parameters: binwidth, bins, pad
train_ens_zero_counts

m2_iter and bc didn’t produce any zero probability outputs. Neither were there any zero probability outputs for combining method logistic regression.
val_ens_nll <- ggplot(data=ens_results) + geom_boxplot(mapping=aes(x=coupling_method, y=nll)) + facet_grid(cols=vars(train_set), rows=vars(combining_method)) +
ggtitle("Comparison of nll for coupling methods for different combiner train methodologies")
val_ens_nll

val_ens_cor_probs$train_type <- "vt"
train_ens_cor_probs$train_type <- "tt"
ens_cor_probs <- rbind(val_ens_cor_probs, train_ens_cor_probs)
ens_cor_preds_histo <- ggplot(data=ens_cor_probs) + geom_histogram(mapping=aes(x=prediction), binwidth=0.01) + facet_grid(rows=vars(combining_method, coupling_method), cols=vars(train_type)) + scale_y_log10() + ggtitle("Probabilities predicted for the correct class")
ens_cor_preds_histo
Warning: Transformation introduced infinite values in continuous y-axis
Warning: Removed 63 rows containing missing values (geom_bar).

In case of logreg combining method there are fewer values in the extreme bins than for lda. Also, there are fewer of these extreme values for tt than for vt.
df_val_Rs <- melt(np$load(file.path(base_dir, "val_training_class_aggr_R.npy")))
df_train_Rs <- melt(np$load(file.path(base_dir, "train_training_class_aggr_R.npy")))
co_m_R <- read.csv(file.path(base_dir, "R_mat_co_m_names.csv"), header=FALSE)
names(df_val_Rs) <- c("combining_method", "precision", "class", "class1", "class2", "prob")
names(df_train_Rs) <- c("combining_method", "precision", "class", "class1", "class2", "prob")
df_val_Rs[,c("class", "class1", "class2", "combining_method")] <- lapply(df_val_Rs[,c("class", "class1", "class2", "combining_method")], as.factor)
df_train_Rs[,c("class", "class1", "class2", "combining_method")] <- lapply(df_train_Rs[,c("class", "class1", "class2", "combining_method")], as.factor)
levels(df_val_Rs$combining_method) <- co_m_R$V1
levels(df_train_Rs$combining_method) <- co_m_R$V1
df_val_Rs$train_type <- "vt"
df_train_Rs$train_type <- "tt"
class_mean_Rs <- rbind(df_val_Rs, df_train_Rs)
df_aggr_Rs_diff <- class_mean_Rs %>% pivot_wider(names_from = train_type, values_from = prob) %>% mutate(val_min_train = vt - tt)
for (cls in 1:classes)
{
cur_class_Rs <- class_mean_Rs %>% filter(class == cls)
plot_cls <- ggplot(cur_class_Rs, aes(x = class2, y = class1)) +
geom_raster(aes(fill=prob)) +
facet_grid(rows=vars(combining_method), cols=vars(train_type)) +
scale_fill_gradient(low="grey90", high="red", limits=c(0, 1)) +
scale_y_discrete(limits=rev, breaks=seq(1, 100, 10)) +
scale_x_discrete(breaks=seq(1, 100, 10)) +
labs(x="class 2", y="class 1", title=paste("Average pairwise probabilities - class ", cls)) +
theme_bw()
print(plot_cls)
}




































































































matrices for logreg are less sharp - this is visible mainly for tt.
for (cls in 1:classes)
{
cur_class_Rs <- df_aggr_Rs_diff %>% filter(class == cls)
plot_cls <- ggplot(cur_class_Rs, aes(x = class2, y = class1)) +
geom_raster(aes(fill=val_min_train)) +
facet_wrap(~combining_method) +
scale_fill_binned(type="viridis", limits=c(-0.3, 0.3), name="validation minus training") +
scale_y_discrete(limits=rev, breaks=seq(1, 100, 10)) +
scale_x_discrete(breaks=seq(1, 100, 10)) +
labs(x="class 2", y="class 1", title=paste("Differences between average pairwise probabilities - class ", cls)) +
theme_bw()
print(plot_cls)
}




































































































Logistic regression without intercept has lower differences between tt and vt R matrices than other combining methods.
combiner_coefs <- load_combiner_coefs(base_dir, repls, folds)
for (cl1 in 1:(10 - 1))
{
for (cl2 in (cl1 + 1):10)
{
cur_plt <- combiner_coefs %>% filter(class1 == cl1 & class2 == cl2) %>% ggplot() + geom_boxplot(aes(x=coefficient, y=value)) +
facet_grid(cols=vars(train_type), rows=vars(combining_method)) + ggtitle(paste("Coefficients for class", cl1, "vs", cl2))
print(cur_plt)
}
}













































for (cl1 in 1:(10 - 1))
{
for (cl2 in (cl1 + 1):10)
{
cur_plt <- combiner_coefs %>% filter(class1 == cl1 & class2 == cl2 & combining_method!="lda") %>% ggplot() + geom_boxplot(aes(x=coefficient, y=value)) +
facet_grid(cols=vars(train_type), rows=vars(combining_method)) + ggtitle(paste("Coefficients for class", cl1, "vs", cl2))
print(cur_plt)
}
}













































In case of lda train_training coefficients tend to have higher variance, in case of logreg it seem to be the oposite, val_training coefficients have higher variance.
avg_combiner_coefs <- combiner_coefs %>% filter(coefficient != "interc") %>% group_by(class1, class2, precision, train_type, coefficient, combining_method) %>% summarise( value = mean(value)) %>% ungroup()
`summarise()` has grouped output by 'class1', 'class2', 'precision', 'train_type', 'coefficient'. You can override using the `.groups` argument.
avg_combiner_c_w <- pivot_wider(avg_combiner_coefs, names_from = coefficient, values_from = value)
avg_combiner_c_w[, c("class1", "class2")] <- lapply(avg_combiner_c_w[, c("class1", "class2")], as.factor)
avg_combiner_c_w$top_net <- factor(c("densenet121", "resnet34", "xception")[max.col(as.matrix(avg_combiner_c_w[, c("densenet121", "resnet34", "xception")]))])
coefs_grid <- ggplot(avg_combiner_c_w, aes(x=class2, y=class1, fill=top_net)) +
geom_raster() +
scale_fill_brewer(type="qual") +
facet_grid(cols=vars(train_type), rows=vars(combining_method)) +
scale_y_discrete(limits=rev, breaks=seq(0, 100, 10)) +
scale_x_discrete(breaks=seq(0, 100, 10)) +
guides(fill=guide_legend(title="Network")) +
xlab("Class") +
ylab("Class") +
ggtitle("Network with highest lda weight for class pairs") +
theme(plot.title = element_text(hjust = 0.5),
axis.ticks = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank())
coefs_grid

LS0tDQp0aXRsZTogIk91dHB1dHMgaW5zcGVjdGlvbiBoYWxmIENJRkFSMTAwIg0Kb3V0cHV0Og0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KLS0tDQoNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KCJnZ3B1YnIiKQ0KbGlicmFyeShMREFUUykNCmxpYnJhcnkoZ2dWZW5uRGlhZ3JhbSkNCmxpYnJhcnkoc3RyaW5ncikNCmxpYnJhcnkoYWJpbmQpDQoNCnNvdXJjZSgidXRpbHMuUiIpDQpgYGANCg0KVmlzdWFsaXphdGlvbiBvbiBDSUZBUjEwMC4NCldlIGFyZSB1c2luZyBkYXRhIG9mIHRocmVlIG5ldXJhbCBuZXR3b3JrcyB0cmFpbmVkIG9uIHJlZHVjZWQgQ0lGQVIxMDAgdHJhaW5pbmcgc2V0LiBIYWxmIG9mIHRoZSBDSUZBUjEwMCB0cmFpbmluZyBzZXQgd2FzIGV4dHJhY3RlZCBhcyBhIHZhbGlkYXRpb24gc2V0LiBXZSB0aGVuIGRpdmlkZWQgYm90aCB0aGUgcmVkdWNlZCB0cmFpbmluZyBzZXQgYW5kIHZhbGlkYXRpb24gc2V0IGludG8gNSBkaXNqb2ludCBzdWJzZXRzIGFuZCB0cmFpbmVkIGFuIGVuc2VtYmxlIG9uIGVhY2ggb2YgdGhlbS4gVGhpcyB3YXMgZG9uZSBpbiAxMCByZXBsaWNhdGlvbnMsIGVhY2ggdGltZSB3aXRoIHJhbmRvbSBzcGxpdCBvZiB0aGUgdHJhaW5pbmcgc2V0IGludG8gdmFsaWRhdGlvbiBhbmQgbmV3IHRyYWluaW5nIHNldC4NCkluIHRoaXMgdmlzdWFsaXphdGlvbiwgd2UgYXJlIHRyeWluZyB0byBpbnNwZWN0IHRoZSBvdXRwdXRzIGRlZXBlciwgbWFpbmx5IHRvIG1ha2Ugc2Vuc2Ugb2Ygc3RyYW5nZSBiZWhhdmlvciBvZiBubGwgbWV0cmljIGZvciBlbnNlbWJsZSBvdXRwdXRzLg0KDQpgYGB7cn0NCmJhc2VfZGlyIDwtICIuLi9kYXRhL2RhdGFfdHJhaW5fdmFsX2hhbGZfYzEwMCINCnJlcGxzIDwtIDA6OQ0KZm9sZHMgPC0gMDo0DQpjbGFzc2VzIDwtIDEwMA0KDQpuZXRzX291dHB1dHMgPC0gbG9hZF9uZXR3b3JrX291dHB1dHMoYmFzZV9kaXIsIHJlcGxzKQ0KZW5zX291dHB1dHNfZ2F0aGVyZWQgPC0gbG9hZF9lbnNlbWJsZV9vdXRwdXRzKGJhc2VfZGlyLCByZXBscywgZm9sZHMsIGdhdGhlcj0oMSArIG5ldHNfb3V0cHV0cyR0ZXN0X2xhYmVsc1sxLCBdKSkNCm5ldF9yZXN1bHRzIDwtIHJlYWQuY3N2KGZpbGUucGF0aChiYXNlX2RpciwgIm5ldF9hY2N1cmFjaWVzLmNzdiIpKQ0KZW5zX3Jlc3VsdHMgPC0gcmVhZC5jc3YoZmlsZS5wYXRoKGJhc2VfZGlyLCAiZW5zZW1ibGVfYWNjdXJhY2llcy5jc3YiKSkNCmBgYA0KYGBge3J9DQpzb3J0X2luZCA8LSBmdW5jdGlvbihsc3QpDQp7DQogIHJldHVybihzb3J0KGxzdCwgaW5kZXgucmV0dXJuPVRSVUUsIGRlY3JlYXNpbmc9VFJVRSkkaXgpDQp9DQpuZXRzX3Rlc3RfdG9wX2luZGljZXMgPC0gYXBwbHkoWD1uZXRzX291dHB1dHMkdGVzdF9vdXRwdXRzLCBNQVJHSU49YygxLCAyLCAzKSwgRlVOPXNvcnRfaW5kKVsxLCAsICwgXQ0Kcl9uIDwtIGxlbmd0aChyZXBscykNCnNhbXBsZXNfbiA8LSBkaW0obmV0c19vdXRwdXRzJHRlc3RfbGFiZWxzKVsyXQ0KbmV0c19uIDwtIGxlbmd0aChuZXRzX291dHB1dHMkbmV0d29ya3MpDQp0ZXN0X2xhYnMgPC0gbmV0c19vdXRwdXRzJHRlc3RfbGFiZWxzICsgMQ0KZGltKHRlc3RfbGFicykgPC0gYyhyX24sIDEsIHNhbXBsZXNfbikNCnRlc3RfbGFicyA8LSBhcGVybShhYmluZChhcnJheShyZXAoYXBlcm0odGVzdF9sYWJzLCBwZXJtPWMoMiwgMSwgMykpLCBuZXRzX24pLCBjKHJfbiwgc2FtcGxlc19uLCBuZXRzX24pKSwgYWxvbmc9MyksIHBlcm09YygxLCAzLCAyKSkNCm5ldHNfdGVzdF9jb3JfcHJlZHMgPC0gdGVzdF9sYWJzID09IG5ldHNfdGVzdF90b3BfaW5kaWNlcw0KYGBgDQoNCmBgYHtyfQ0KZm9yIChyaSBpbiAxOnJfbikNCnsNCiAgbmV0c19jb3JfbGlzdCA8LSBsaXN0KCkNCiAgaW5jb3IgPC0gMTpzYW1wbGVzX24NCiAgZm9yIChuaSBpbiAxOm5ldHNfbikNCiAgew0KICAgIGNvcl9saXN0IDwtIHdoaWNoKG5ldHNfdGVzdF9jb3JfcHJlZHNbcmksIG5pLCBdKQ0KICAgIG5ldHNfY29yX2xpc3RbW25ldHNfb3V0cHV0cyRuZXR3b3Jrc1tuaV1dXSA9IGNvcl9saXN0DQogICAgaW5jb3IgPC0gc2V0ZGlmZihpbmNvciwgY29yX2xpc3QpDQogIH0NCiAgaW5jb3JfbiA8LSBsZW5ndGgoaW5jb3IpDQogIHZlbm5fZGlhZyA8LSBnZ1Zlbm5EaWFncmFtKG5ldHNfY29yX2xpc3QpICsgc2NhbGVfZmlsbF9ncmFkaWVudCh0cmFucz0ibG9nMTAiLCBuYW1lPSJjb3VudCIsIGxpbWl0cz1jKDEsIDEwMDAwKSkgKw0KICAgIGFubm90YXRlKGdlb209InRleHQiLCB4PS00LCB5PTUsIGxhYmVsPXBhc3RlKCJJbmNvcnJlY3QgIiwgaW5jb3JfbiwgIlxuIiwgcm91bmQoaW5jb3JfbiAvIHNhbXBsZXNfbiAqIDEwMCksICIlIikpICsNCiAgICBnZ3RpdGxlKHBhc3RlKCJDb3JyZWN0IHByZWRpY3Rpb25zIGJ5IG5ldHdvcmsgLSByZXBsaWNhdGlvbiAiLCByaSkpICsNCiAgICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzPWMoLTgsIDEwKSkNCiAgcHJpbnQodmVubl9kaWFnKQ0KfQ0KYGBgDQpDb21wYXJlZCB0byBDSUZBUjEwIGV4YW1wbGVzLCBoZXJlIGFyZSBmZXdlciBzYW1wbGVzIGNvcnJlY3RseSBjbGFzc2lmaWVkIGJ5IGFsbCB0aGUgbmV0d29ya3MuIEZvciBvbmUgbmV0d29yayBleGNsdXNpdmUgY29ycmVjdCBjbGFzc2lmaWNhdGlvbnMgZGVuc2VuZXQgYW5kIHhjZXB0aW9uIHBlcmZvcm0gYmV0dGVyIHRoYW4gcmVzbmV0LiBQYWlycyBvZiBuZXR3b3JrIGFsbCBoYXZlIHNpbWlsYXIgdmFsdWVzIHdpdGggeGNlcHRpb24sIHJlc25ldCBsYWdnaW5nIGEgYml0Lg0KDQpgYGB7cn0NCnByZWRzIDwtIG5ldHNfb3V0cHV0cyR0ZXN0X291dHB1dHMNCmZvciAocmkgaW4gcmVwbHMgKyAxKQ0Kew0KICBmb3IgKG5ldF9pIGluIHNlcV9hbG9uZyhuZXRzX291dHB1dHNbWyJuZXR3b3JrcyJdXSkpDQogIHsNCiAgICBwcmVkc1tyaSwgbmV0X2ksICxdIDwtIHNvZnRtYXgocHJlZHNbcmksIG5ldF9pLCAsIF0pDQogIH0NCn0NCm5ldHNfdGVzdF9jb3JfcHJvYnMgPC0gZ2F0aGVyKHByZWRzLCAxICsgbmV0c19vdXRwdXRzJHRlc3RfbGFiZWxzWzEsIF0sIDMsIDQpDQpuZXRzX3Rlc3RfY29yX3Byb2JzIDwtIG1lbHQobmV0c190ZXN0X2Nvcl9wcm9icykNCm5ldHNfdGVzdF9jb3JfcHJvYnMgPC0gbmV0c190ZXN0X2Nvcl9wcm9ic1ssIGMoLTMsIC00KV0NCm5hbWVzKG5ldHNfdGVzdF9jb3JfcHJvYnMpIDwtIGMoInJlcGxpY2F0aW9uIiwgIm5ldHdvcmsiLCAicHJlZGljdGlvbiIpDQpuZXRzX3Rlc3RfY29yX3Byb2JzJG5ldHdvcmsgPC0gYXMuZmFjdG9yKG5ldHNfdGVzdF9jb3JfcHJvYnMkbmV0d29yaykNCmxldmVscyhuZXRzX3Rlc3RfY29yX3Byb2JzJG5ldHdvcmspIDwtIG5ldHNfb3V0cHV0cyRuZXR3b3Jrcw0KYGBgDQoNCmBgYHtyfQ0KbmV0c19jb3JfcHJlZHNfaGlzdG8gPC0gZ2dwbG90KGRhdGE9bmV0c190ZXN0X2Nvcl9wcm9icykgKyBnZW9tX2hpc3RvZ3JhbShtYXBwaW5nPWFlcyh4PXByZWRpY3Rpb24pLCBiaW53aWR0aD0wLjAxKSArDQogIGdndGl0bGUoIkhpc3RvZ3JhbXMgb2YgcHJlZGljdGVkIHByb2JhYmlsaXR5IGZvciB0aGUgY29ycmVjdCBjbGFzcyIpICsgZmFjZXRfd3JhcCh+bmV0d29yaykgKyBzY2FsZV95X2xvZzEwKCkNCm5ldHNfY29yX3ByZWRzX2hpc3RvDQpgYGANCg0KDQpgYGB7cn0NCnZhbF9lbnNfY29yX3Byb2JzIDwtIG1lbHQoZW5zX291dHB1dHNfZ2F0aGVyZWQkdmFsX3RyYWluaW5nKQ0KdmFsX2Vuc19jb3JfcHJvYnMgPC0gdmFsX2Vuc19jb3JfcHJvYnNbLCBjKC01LCAtNildDQpuYW1lcyh2YWxfZW5zX2Nvcl9wcm9icykgPC0gYygicmVwbGljYXRpb24iLCAiY29tYmluaW5nX21ldGhvZCIsICJjb3VwbGluZ19tZXRob2QiLCAiZm9sZCIsICJwcmVkaWN0aW9uIikNCnZhbF9lbnNfY29yX3Byb2JzWywgYygiY29tYmluaW5nX21ldGhvZCIsICJjb3VwbGluZ19tZXRob2QiKV0gPC0gbGFwcGx5KHZhbF9lbnNfY29yX3Byb2JzWywgYygiY29tYmluaW5nX21ldGhvZCIsICJjb3VwbGluZ19tZXRob2QiKV0sIGFzLmZhY3RvcikNCmxldmVscyh2YWxfZW5zX2Nvcl9wcm9icyRjb21iaW5pbmdfbWV0aG9kKSA8LSBlbnNfb3V0cHV0c19nYXRoZXJlZCRjb21iaW5pbmdfbWV0aG9kcw0KbGV2ZWxzKHZhbF9lbnNfY29yX3Byb2JzJGNvdXBsaW5nX21ldGhvZCkgPC0gZW5zX291dHB1dHNfZ2F0aGVyZWQkY291cGxpbmdfbWV0aG9kcw0KYGBgDQoNCmBgYHtyLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9OH0NCnZhbF9lbnNfY29yX3ByZWRzX2hpc3RvIDwtIGdncGxvdChkYXRhPXZhbF9lbnNfY29yX3Byb2JzKSArIGdlb21faGlzdG9ncmFtKG1hcHBpbmc9YWVzKHg9cHJlZGljdGlvbiksIGJpbndpZHRoPTAuMDEpICsgZmFjZXRfZ3JpZChyb3dzPXZhcnMoY29tYmluaW5nX21ldGhvZCksIGNvbHM9dmFycyhjb3VwbGluZ19tZXRob2QpKSArIHNjYWxlX3lfbG9nMTAoKSArIGdndGl0bGUoIlByb2JhYmlsaXRpZXMgcHJlZGljdGVkIGZvciB0aGUgY29ycmVjdCBjbGFzcyAtIGVucyB0cmFpbmVkIG9uIHZhbCIpDQp2YWxfZW5zX2Nvcl9wcmVkc19oaXN0bw0KYGBgDQoNCmBgYHtyfQ0Kc3VtKHZhbF9lbnNfY29yX3Byb2JzJHByZWRpY3Rpb24gPD0gMCkNCmBgYA0KTm8gemVybyBwcm9iYWJpbGl0aWVzIHByb2R1Y2VkDQoNCmBgYHtyfQ0KdHJhaW5fZW5zX2Nvcl9wcm9icyA8LSBtZWx0KGVuc19vdXRwdXRzX2dhdGhlcmVkJHRyYWluX3RyYWluaW5nKQ0KdHJhaW5fZW5zX2Nvcl9wcm9icyA8LSB0cmFpbl9lbnNfY29yX3Byb2JzWywgYygtNSwgLTYpXQ0KbmFtZXModHJhaW5fZW5zX2Nvcl9wcm9icykgPC0gYygicmVwbGljYXRpb24iLCAiY29tYmluaW5nX21ldGhvZCIsICJjb3VwbGluZ19tZXRob2QiLCAiZm9sZCIsICJwcmVkaWN0aW9uIikNCnRyYWluX2Vuc19jb3JfcHJvYnNbLCBjKCJjb21iaW5pbmdfbWV0aG9kIiwgImNvdXBsaW5nX21ldGhvZCIpXSA8LSBsYXBwbHkodHJhaW5fZW5zX2Nvcl9wcm9ic1ssIGMoImNvbWJpbmluZ19tZXRob2QiLCAiY291cGxpbmdfbWV0aG9kIildLCBhcy5mYWN0b3IpDQpsZXZlbHModHJhaW5fZW5zX2Nvcl9wcm9icyRjb21iaW5pbmdfbWV0aG9kKSA8LSBlbnNfb3V0cHV0c19nYXRoZXJlZCRjb21iaW5pbmdfbWV0aG9kcw0KbGV2ZWxzKHRyYWluX2Vuc19jb3JfcHJvYnMkY291cGxpbmdfbWV0aG9kKSA8LSBlbnNfb3V0cHV0c19nYXRoZXJlZCRjb3VwbGluZ19tZXRob2RzDQpgYGANCg0KDQpgYGB7ciwgZmlnLndpZHRoPTE0LCBmaWcuaGVpZ2h0PTh9DQp0cmFpbl9lbnNfY29yX3ByZWRzX2hpc3RvIDwtIGdncGxvdChkYXRhPXRyYWluX2Vuc19jb3JfcHJvYnMpICsgZ2VvbV9oaXN0b2dyYW0obWFwcGluZz1hZXMoeD1wcmVkaWN0aW9uKSwgYmlud2lkdGg9MC4wMSkgKyBmYWNldF9ncmlkKHJvd3M9dmFycyhjb21iaW5pbmdfbWV0aG9kKSwgY29scz12YXJzKGNvdXBsaW5nX21ldGhvZCkpICsgc2NhbGVfeV9sb2cxMCgpICsgZ2d0aXRsZSgiUHJvYmFiaWxpdGllcyBwcmVkaWN0ZWQgZm9yIHRoZSBjb3JyZWN0IGNsYXNzIC0gZW5zIHRyYWluZWQgb24gdHJhaW4iKQ0KdHJhaW5fZW5zX2Nvcl9wcmVkc19oaXN0bw0KYGBgDQpTdHJhbmdlIGJlaGF2aW9yIG9ic2VydmVkIGZvciBiYyBjb3VwbGluZyBtZXRob2QgaW4gY2FzZSBvZiBjb21iaW5pbmcgbWV0aG9kIGxkYSBpcyBub3QgcHJlc2VudCBmb3IgY29tYmluaW5nIG1ldGhvZHMgbG9ncmVnLg0KDQpgYGB7cn0NCnRyYWluX2Vuc196ZXJvX2NvdW50cyA8LSBnZ3Bsb3QoZGF0YT10cmFpbl9lbnNfY29yX3Byb2JzW3RyYWluX2Vuc19jb3JfcHJvYnMkcHJlZGljdGlvbiA8PSAwLCBdKSArIGdlb21faGlzdG9ncmFtKG1hcHBpbmc9YWVzKHg9Y291cGxpbmdfbWV0aG9kKSwgc3RhdD0iY291bnQiKSArIGZhY2V0X3dyYXAofmNvbWJpbmluZ19tZXRob2QpICArIGdndGl0bGUoIkNvdW50cyBvZiB6ZXJvIG9yIGxvd2VyIHByb2JhYmlsaXRpZXMgcHJlZGljdGVkIGZvciB0aGUgY29ycmVjdCBjbGFzcyBieSBjb3VwIG1cblRyYWluIHRyYWluaW5nIikNCnRyYWluX2Vuc196ZXJvX2NvdW50cw0KYGBgDQoNCm0yX2l0ZXIgYW5kIGJjIGRpZG4ndCBwcm9kdWNlIGFueSB6ZXJvIHByb2JhYmlsaXR5IG91dHB1dHMuIE5laXRoZXIgd2VyZSB0aGVyZSBhbnkgemVybyBwcm9iYWJpbGl0eSBvdXRwdXRzIGZvciBjb21iaW5pbmcgbWV0aG9kIGxvZ2lzdGljIHJlZ3Jlc3Npb24uDQoNCmBgYHtyfQ0KdmFsX2Vuc19ubGwgPC0gZ2dwbG90KGRhdGE9ZW5zX3Jlc3VsdHMpICsgZ2VvbV9ib3hwbG90KG1hcHBpbmc9YWVzKHg9Y291cGxpbmdfbWV0aG9kLCB5PW5sbCkpICsgZmFjZXRfZ3JpZChjb2xzPXZhcnModHJhaW5fc2V0KSwgcm93cz12YXJzKGNvbWJpbmluZ19tZXRob2QpKSArDQogIGdndGl0bGUoIkNvbXBhcmlzb24gb2YgbmxsIGZvciBjb3VwbGluZyBtZXRob2RzIGZvciBkaWZmZXJlbnQgY29tYmluZXIgdHJhaW4gbWV0aG9kb2xvZ2llcyIpDQp2YWxfZW5zX25sbA0KYGBgDQoNCg0KYGBge3J9DQp2YWxfZW5zX2Nvcl9wcm9icyR0cmFpbl90eXBlIDwtICJ2dCINCnRyYWluX2Vuc19jb3JfcHJvYnMkdHJhaW5fdHlwZSA8LSAidHQiDQplbnNfY29yX3Byb2JzIDwtIHJiaW5kKHZhbF9lbnNfY29yX3Byb2JzLCB0cmFpbl9lbnNfY29yX3Byb2JzKQ0KYGBgDQoNCmBgYHtyLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9OH0NCmVuc19jb3JfcHJlZHNfaGlzdG8gPC0gZ2dwbG90KGRhdGE9ZW5zX2Nvcl9wcm9icykgKyBnZW9tX2hpc3RvZ3JhbShtYXBwaW5nPWFlcyh4PXByZWRpY3Rpb24pLCBiaW53aWR0aD0wLjAxKSArIGZhY2V0X2dyaWQocm93cz12YXJzKGNvbWJpbmluZ19tZXRob2QsIGNvdXBsaW5nX21ldGhvZCksIGNvbHM9dmFycyh0cmFpbl90eXBlKSkgKyBzY2FsZV95X2xvZzEwKCkgKyBnZ3RpdGxlKCJQcm9iYWJpbGl0aWVzIHByZWRpY3RlZCBmb3IgdGhlIGNvcnJlY3QgY2xhc3MiKQ0KZW5zX2Nvcl9wcmVkc19oaXN0bw0KYGBgDQpJbiBjYXNlIG9mIGxvZ3JlZyBjb21iaW5pbmcgbWV0aG9kIHRoZXJlIGFyZSBmZXdlciB2YWx1ZXMgaW4gdGhlIGV4dHJlbWUgYmlucyB0aGFuIGZvciBsZGEuIEFsc28sIHRoZXJlIGFyZSBmZXdlciBvZiB0aGVzZSBleHRyZW1lIHZhbHVlcyBmb3IgdHQgdGhhbiBmb3IgdnQuDQoNCg0KYGBge3J9DQpkZl92YWxfUnMgPC0gbWVsdChucCRsb2FkKGZpbGUucGF0aChiYXNlX2RpciwgInZhbF90cmFpbmluZ19jbGFzc19hZ2dyX1IubnB5IikpKQ0KZGZfdHJhaW5fUnMgPC0gbWVsdChucCRsb2FkKGZpbGUucGF0aChiYXNlX2RpciwgInRyYWluX3RyYWluaW5nX2NsYXNzX2FnZ3JfUi5ucHkiKSkpDQpjb19tX1IgPC0gcmVhZC5jc3YoZmlsZS5wYXRoKGJhc2VfZGlyLCAiUl9tYXRfY29fbV9uYW1lcy5jc3YiKSwgaGVhZGVyPUZBTFNFKQ0KYGBgDQoNCmBgYHtyfQ0KbmFtZXMoZGZfdmFsX1JzKSA8LSBjKCJjb21iaW5pbmdfbWV0aG9kIiwgInByZWNpc2lvbiIsICJjbGFzcyIsICJjbGFzczEiLCAiY2xhc3MyIiwgInByb2IiKQ0KbmFtZXMoZGZfdHJhaW5fUnMpIDwtIGMoImNvbWJpbmluZ19tZXRob2QiLCAicHJlY2lzaW9uIiwgImNsYXNzIiwgImNsYXNzMSIsICJjbGFzczIiLCAicHJvYiIpDQoNCmRmX3ZhbF9Sc1ssYygiY2xhc3MiLCAiY2xhc3MxIiwgImNsYXNzMiIsICJjb21iaW5pbmdfbWV0aG9kIildIDwtIGxhcHBseShkZl92YWxfUnNbLGMoImNsYXNzIiwgImNsYXNzMSIsICJjbGFzczIiLCAiY29tYmluaW5nX21ldGhvZCIpXSwgYXMuZmFjdG9yKQ0KZGZfdHJhaW5fUnNbLGMoImNsYXNzIiwgImNsYXNzMSIsICJjbGFzczIiLCAiY29tYmluaW5nX21ldGhvZCIpXSA8LSBsYXBwbHkoZGZfdHJhaW5fUnNbLGMoImNsYXNzIiwgImNsYXNzMSIsICJjbGFzczIiLCAiY29tYmluaW5nX21ldGhvZCIpXSwgYXMuZmFjdG9yKQ0KDQpsZXZlbHMoZGZfdmFsX1JzJGNvbWJpbmluZ19tZXRob2QpIDwtIGNvX21fUiRWMQ0KbGV2ZWxzKGRmX3RyYWluX1JzJGNvbWJpbmluZ19tZXRob2QpIDwtIGNvX21fUiRWMQ0KDQpgYGANCg0KYGBge3J9DQpkZl92YWxfUnMkdHJhaW5fdHlwZSA8LSAidnQiDQpkZl90cmFpbl9ScyR0cmFpbl90eXBlIDwtICJ0dCINCmNsYXNzX21lYW5fUnMgPC0gcmJpbmQoZGZfdmFsX1JzLCBkZl90cmFpbl9ScykNCg0KZGZfYWdncl9Sc19kaWZmIDwtIGNsYXNzX21lYW5fUnMgJT4lIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSB0cmFpbl90eXBlLCB2YWx1ZXNfZnJvbSA9IHByb2IpICU+JSBtdXRhdGUodmFsX21pbl90cmFpbiA9IHZ0IC0gdHQpDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTZ9DQpmb3IgKGNscyBpbiAxOmNsYXNzZXMpDQp7DQogIGN1cl9jbGFzc19ScyA8LSBjbGFzc19tZWFuX1JzICU+JSBmaWx0ZXIoY2xhc3MgPT0gY2xzKQ0KICBwbG90X2NscyA8LSAgZ2dwbG90KGN1cl9jbGFzc19ScywgYWVzKHggPSBjbGFzczIsIHkgPSBjbGFzczEpKSArIA0KICAgIGdlb21fcmFzdGVyKGFlcyhmaWxsPXByb2IpKSArIA0KICAgIGZhY2V0X2dyaWQocm93cz12YXJzKGNvbWJpbmluZ19tZXRob2QpLCBjb2xzPXZhcnModHJhaW5fdHlwZSkpICsNCiAgICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0iZ3JleTkwIiwgaGlnaD0icmVkIiwgbGltaXRzPWMoMCwgMSkpICsNCiAgICBzY2FsZV95X2Rpc2NyZXRlKGxpbWl0cz1yZXYsIGJyZWFrcz1zZXEoMSwgMTAwLCAxMCkpICsNCiAgICBzY2FsZV94X2Rpc2NyZXRlKGJyZWFrcz1zZXEoMSwgMTAwLCAxMCkpICsNCiAgICBsYWJzKHg9ImNsYXNzIDIiLCB5PSJjbGFzcyAxIiwgdGl0bGU9cGFzdGUoIkF2ZXJhZ2UgcGFpcndpc2UgcHJvYmFiaWxpdGllcyAtIGNsYXNzICIsIGNscykpICsNCiAgICB0aGVtZV9idygpDQogIA0KICBwcmludChwbG90X2NscykNCn0NCmBgYA0KbWF0cmljZXMgZm9yIGxvZ3JlZyBhcmUgbGVzcyBzaGFycCAtIHRoaXMgaXMgdmlzaWJsZSBtYWlubHkgZm9yIHR0Lg0KDQpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9M30NCmZvciAoY2xzIGluIDE6Y2xhc3NlcykNCnsNCiAgY3VyX2NsYXNzX1JzIDwtIGRmX2FnZ3JfUnNfZGlmZiAlPiUgZmlsdGVyKGNsYXNzID09IGNscykNCiAgcGxvdF9jbHMgPC0gIGdncGxvdChjdXJfY2xhc3NfUnMsIGFlcyh4ID0gY2xhc3MyLCB5ID0gY2xhc3MxKSkgKyANCiAgICBnZW9tX3Jhc3RlcihhZXMoZmlsbD12YWxfbWluX3RyYWluKSkgKyANCiAgICBmYWNldF93cmFwKH5jb21iaW5pbmdfbWV0aG9kKSArDQogICAgc2NhbGVfZmlsbF9iaW5uZWQodHlwZT0idmlyaWRpcyIsIGxpbWl0cz1jKC0wLjMsIDAuMyksIG5hbWU9InZhbGlkYXRpb24gbWludXMgdHJhaW5pbmciKSArDQogICAgc2NhbGVfeV9kaXNjcmV0ZShsaW1pdHM9cmV2LCBicmVha3M9c2VxKDEsIDEwMCwgMTApKSArDQogICAgc2NhbGVfeF9kaXNjcmV0ZShicmVha3M9c2VxKDEsIDEwMCwgMTApKSArDQogICAgbGFicyh4PSJjbGFzcyAyIiwgeT0iY2xhc3MgMSIsIHRpdGxlPXBhc3RlKCJEaWZmZXJlbmNlcyBiZXR3ZWVuIGF2ZXJhZ2UgcGFpcndpc2UgcHJvYmFiaWxpdGllcyAtIGNsYXNzICIsIGNscykpICsNCiAgICB0aGVtZV9idygpDQogIA0KICBwcmludChwbG90X2NscykNCn0NCmBgYA0KTG9naXN0aWMgcmVncmVzc2lvbiB3aXRob3V0IGludGVyY2VwdCBoYXMgbG93ZXIgZGlmZmVyZW5jZXMgYmV0d2VlbiB0dCBhbmQgdnQgUiBtYXRyaWNlcyB0aGFuIG90aGVyIGNvbWJpbmluZyBtZXRob2RzLiANCg0KYGBge3J9DQpjb21iaW5lcl9jb2VmcyA8LSBsb2FkX2NvbWJpbmVyX2NvZWZzKGJhc2VfZGlyLCByZXBscywgZm9sZHMpDQpgYGANCg0KYGBge3J9DQpmb3IgKGNsMSBpbiAxOigxMCAtIDEpKQ0Kew0KICBmb3IgKGNsMiBpbiAoY2wxICsgMSk6MTApDQogIHsNCiAgICBjdXJfcGx0IDwtIGNvbWJpbmVyX2NvZWZzICU+JSBmaWx0ZXIoY2xhc3MxID09IGNsMSAmIGNsYXNzMiA9PSBjbDIpICU+JSBnZ3Bsb3QoKSArIGdlb21fYm94cGxvdChhZXMoeD1jb2VmZmljaWVudCwgeT12YWx1ZSkpICsNCiAgICAgIGZhY2V0X2dyaWQoY29scz12YXJzKHRyYWluX3R5cGUpLCByb3dzPXZhcnMoY29tYmluaW5nX21ldGhvZCkpICsgZ2d0aXRsZShwYXN0ZSgiQ29lZmZpY2llbnRzIGZvciBjbGFzcyIsIGNsMSwgInZzIiwgY2wyKSkNCiAgICBwcmludChjdXJfcGx0KQ0KICB9DQp9DQpgYGANCg0KYGBge3J9DQpmb3IgKGNsMSBpbiAxOigxMCAtIDEpKQ0Kew0KICBmb3IgKGNsMiBpbiAoY2wxICsgMSk6MTApDQogIHsNCiAgICBjdXJfcGx0IDwtIGNvbWJpbmVyX2NvZWZzICU+JSBmaWx0ZXIoY2xhc3MxID09IGNsMSAmIGNsYXNzMiA9PSBjbDIgJiBjb21iaW5pbmdfbWV0aG9kIT0ibGRhIikgJT4lIGdncGxvdCgpICsgZ2VvbV9ib3hwbG90KGFlcyh4PWNvZWZmaWNpZW50LCB5PXZhbHVlKSkgKw0KICAgICAgZmFjZXRfZ3JpZChjb2xzPXZhcnModHJhaW5fdHlwZSksIHJvd3M9dmFycyhjb21iaW5pbmdfbWV0aG9kKSkgKyBnZ3RpdGxlKHBhc3RlKCJDb2VmZmljaWVudHMgZm9yIGNsYXNzIiwgY2wxLCAidnMiLCBjbDIpKQ0KICAgIHByaW50KGN1cl9wbHQpDQogIH0NCn0NCmBgYA0KSW4gY2FzZSBvZiBsZGEgdHJhaW5fdHJhaW5pbmcgY29lZmZpY2llbnRzIHRlbmQgdG8gaGF2ZSBoaWdoZXIgdmFyaWFuY2UsIGluIGNhc2Ugb2YgbG9ncmVnIGl0IHNlZW0gdG8gYmUgdGhlIG9wb3NpdGUsIHZhbF90cmFpbmluZyBjb2VmZmljaWVudHMgaGF2ZSBoaWdoZXIgdmFyaWFuY2UuDQoNCmBgYHtyfQ0KYXZnX2NvbWJpbmVyX2NvZWZzIDwtIGNvbWJpbmVyX2NvZWZzICU+JSBmaWx0ZXIoY29lZmZpY2llbnQgIT0gImludGVyYyIpICU+JSBncm91cF9ieShjbGFzczEsIGNsYXNzMiwgcHJlY2lzaW9uLCB0cmFpbl90eXBlLCBjb2VmZmljaWVudCwgY29tYmluaW5nX21ldGhvZCkgJT4lIHN1bW1hcmlzZSggdmFsdWUgPSBtZWFuKHZhbHVlKSkgJT4lIHVuZ3JvdXAoKQ0KDQphdmdfY29tYmluZXJfY193IDwtIHBpdm90X3dpZGVyKGF2Z19jb21iaW5lcl9jb2VmcywgbmFtZXNfZnJvbSA9IGNvZWZmaWNpZW50LCB2YWx1ZXNfZnJvbSA9IHZhbHVlKQ0KYXZnX2NvbWJpbmVyX2Nfd1ssIGMoImNsYXNzMSIsICJjbGFzczIiKV0gPC0gbGFwcGx5KGF2Z19jb21iaW5lcl9jX3dbLCBjKCJjbGFzczEiLCAiY2xhc3MyIildLCBhcy5mYWN0b3IpDQphdmdfY29tYmluZXJfY193JHRvcF9uZXQgPC0gZmFjdG9yKGMoImRlbnNlbmV0MTIxIiwgInJlc25ldDM0IiwgInhjZXB0aW9uIilbbWF4LmNvbChhcy5tYXRyaXgoYXZnX2NvbWJpbmVyX2Nfd1ssIGMoImRlbnNlbmV0MTIxIiwgInJlc25ldDM0IiwgInhjZXB0aW9uIildKSldKQ0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD03fQ0KY29lZnNfZ3JpZCA8LSBnZ3Bsb3QoYXZnX2NvbWJpbmVyX2NfdywgYWVzKHg9Y2xhc3MyLCB5PWNsYXNzMSwgZmlsbD10b3BfbmV0KSkgKyANCiAgZ2VvbV9yYXN0ZXIoKSArIA0KICBzY2FsZV9maWxsX2JyZXdlcih0eXBlPSJxdWFsIikgKw0KICBmYWNldF9ncmlkKGNvbHM9dmFycyh0cmFpbl90eXBlKSwgcm93cz12YXJzKGNvbWJpbmluZ19tZXRob2QpKSArDQogIHNjYWxlX3lfZGlzY3JldGUobGltaXRzPXJldiwgYnJlYWtzPXNlcSgwLCAxMDAsIDEwKSkgKw0KICBzY2FsZV94X2Rpc2NyZXRlKGJyZWFrcz1zZXEoMCwgMTAwLCAxMCkpICsNCiAgZ3VpZGVzKGZpbGw9Z3VpZGVfbGVnZW5kKHRpdGxlPSJOZXR3b3JrIikpICsNCiAgeGxhYigiQ2xhc3MiKSArIA0KICB5bGFiKCJDbGFzcyIpICsNCiAgZ2d0aXRsZSgiTmV0d29yayB3aXRoIGhpZ2hlc3QgbGRhIHdlaWdodCBmb3IgY2xhc3MgcGFpcnMiKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLA0KICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCANCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSkNCg0KY29lZnNfZ3JpZA0KYGBgDQoNCg==